#pragma once
#include <iostream>
#include <string>
#include <fstream>
#include <memory>
#include <filesystem>
#include <sstream>
#include <ctime>
#include <unistd.h>
#include "cond.hpp"
#include "mutex.hpp"
#include "thread.hpp"

namespace LogModule
{
    using namespace CondModule;
    using namespace MutexModule;
    using namespace MyThread;

    // 规定的输出日志的文件
    const std::string defaultdir = "./log/";
    const std::string defaultfile = "log.txt";

    enum class LEVEL
    {
        DEBUG = 1,
        WARNING,
        INFO,
        ERROR
    };

    class LogStrategy
    {
    public:
        ~LogStrategy() = default;               // 策略的构造方法 缺省
        virtual void LoadMessWay(const std::string& mess) = 0;       // 纯虚函数 子类继承父类之后必须重写 要不然就会报错
    };

    // 刷新方式  利用多态来实现
    class ConsoleLogStrategy : public LogStrategy
    {
    public:
        ConsoleLogStrategy()
        {}
        ~ConsoleLogStrategy()
        {}
        // 往显示器输出消息
        void LoadMessWay(const std::string& mess)
        {
            {
                Lockguard lockguard(_mtx);
                std::cout << mess << std::endl;
            }
        }
    private:
        mutex _mtx;
    };

    class FileLogStrategy : public LogStrategy
    {
    public:
        FileLogStrategy(const std::string& dir = defaultdir, const std::string& file = defaultfile) 
            :_file(file)
            ,_dir(dir)
        {
            Lockguard lockguard(_mtx);
            // 把文件夹和普通文件的名字拼接起来就是文件的地址
            _filename = _dir + _file;
            // 判断这个文件是否存在  C++17语法
            if(std::filesystem::exists(dir)) return;

            try
            {
                // 尝试一下 创建一个文件
                std::filesystem::create_directories(dir);
            }
            // 捕获一个特定的信号
            catch(const std::filesystem::filesystem_error& e)
            {
                std::cerr << e.what() << '\n';
            }
            
        }
        void LoadMessWay(const std::string& mess)
        {
            Lockguard lockguard(_mtx);
            // 用追加的方式写入文件 
            std::fstream out(_filename.c_str(), std::ios::app);
            out << mess << '\n';

            out.close();
        }
        ~FileLogStrategy() {}
    private:
        std::string _filename;
        std::string _file;
        std::string _dir;
        mutex _mtx;
    };

    std::string CurrentTime()
    {
        time_t cur = time(nullptr);
        struct tm info;
        // localtime 可以转化时间戳为结构体 方便理解
        localtime_r(&cur, &info);
        char buffer[1024];

        snprintf(buffer, sizeof(buffer), "%4d-%02d-%02d %02d:%02d:%02d", 
                info.tm_year + 1900, info.tm_mon + 1, info.tm_mday
            ,info.tm_hour, info.tm_min, info.tm_sec);
        return buffer;
    }   

    std::string GetLevel(LogModule::LEVEL num)
    {
        switch(num)
        {
            case LEVEL::DEBUG: return "DEBUG";
            case LEVEL::WARNING: return "WARNING";
            case LEVEL::INFO: return "INFO";
            case LEVEL::ERROR: return "ERROR";
            default: return "UNKNOWN";
        }
    }

    class Logger
    {
    public:
        Logger() 
        {
            // 默认方法用 显示器输出日志
            _logstrategy = std::make_unique<ConsoleLogStrategy>();
        }

        // 下面两种方法用来改变输出日志的方式
        void EnableConsloeLogStrategy()
        {
            _logstrategy = std::make_unique<ConsoleLogStrategy>();
        }
        void EnableFileLogStrategy()
        {
            _logstrategy = std::make_unique<FileLogStrategy>();
        }

        class loginfo
        {
        public:
            loginfo(LogModule::LEVEL level, std::string fname, int line, Logger& logger)
                :_level(level)
                ,_fname(fname)
                ,_line(line)
                ,_pid(getpid())
                ,_time(CurrentTime())
                ,_logger(logger)
            {
                std::stringstream ss;
                ss << "[" << _time << "] " << "[" << GetLevel(_level) << "] "
                << "[" << _pid << "] " << "[" << _fname << "] " << "[" << _line << "] - ";

                _info = ss.str();
            }

            template<typename T>
            // 这里返回值为loginfo& 是为了让它一直给相同的一个info 实现拼接数据
            loginfo& operator<<(const T& mess)
            {
                std::stringstream ss;
                ss << mess;
                _info += ss.str();
                return *this;
            }

            ~loginfo() 
            {
                // 析构时候调用 加载日志的信息
                _logger._logstrategy->LoadMessWay(_info);
            }

        private:
            std::string _time;
            LogModule::LEVEL _level;
            pid_t _pid;
            std::string _fname;
            int _line;
            std::string _info;
            Logger& _logger;
        };

        // 这里就用临时对象
        loginfo operator()(LogModule::LEVEL level, std::string fname, int line)
        {
            // 这里传入*this指针之后 可以访问当前的私有变量 也就是 _logstrategy 也就是log方式
            return loginfo(level, fname, line, *this);
        }

        ~Logger() {}

    private:
        // 这里用到多态来实现 用基类的指针来获取
        std::unique_ptr<LogStrategy> _logstrategy;
    };

    Logger logger;
    #define LOG(level) logger(level, __FILE__, __LINE__)
    #define ENABLEFILELOGSTRATEGY() logger.EnableFileLogStrategy()
    #define ENABLECONSOLELOGSTRATEGY() logger.EnableConsloeLogStrategy()
}